<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>（Vue源码六）Vue 组件原理分析 | Jeffery Blog</title><meta name="keywords" content="vue, 组件化，vue 源码"><meta name="author" content="志非"><meta name="copyright" content="志非"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="vue, vue 源码，Vue 组件化">
<meta property="og:type" content="article">
<meta property="og:title" content="（Vue源码六）Vue 组件原理分析">
<meta property="og:url" content="https://fif.gitee.io/blog/2021/05/04/vue/vue-code-8.component/index.html">
<meta property="og:site_name" content="Jeffery Blog">
<meta property="og:description" content="vue, vue 源码，Vue 组件化">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png">
<meta property="article:published_time" content="2021-05-03T16:00:00.000Z">
<meta property="article:modified_time" content="2021-05-11T16:00:00.000Z">
<meta property="article:author" content="志非">
<meta property="article:tag" content="vue">
<meta property="article:tag" content="vue 源码">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png"><link rel="shortcut icon" href="/blog/img/home/avatar.jpeg"><link rel="canonical" href="https://fif.gitee.io/blog/2021/05/04/vue/vue-code-8.component/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//fonts.googleapis.com" crossorigin=""/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/blog/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.js@2.10.5/dist/instantsearch.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/instantsearch.js@2.10.5/dist/instantsearch.min.js" defer></script><script async="async" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><script>(adsbygoogle = window.adsbygoogle || []).push({
  google_ad_client: '',
  enable_page_level_ads: 'true'
});</script><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Titillium+Web&amp;display=swap" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/blog/',
  algolia: {"appId":"QYKLQ4BSGP","apiKey":"d45fc66694b4260b20711a30fdb5c4e1","indexName":"jeffery-blog","hits":{"per_page":6},"languages":{"input_placeholder":"搜索文章","hits_empty":"找不到您查询的内容：${query}","hits_stats":"找到 ${hits} 条结果，用时 ${time} 毫秒"}},
  localSearch: undefined,
  translate: {"defaultEncoding":2,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"簡"},
  noticeOutdate: {"limitDay":200,"position":"top","messagePrev":"本文修改于","messageNext":"天前，如果本文有一定的时效性，那么本文可能已经失效~"},
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: true,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'mediumZoom',
  Snackbar: {"chs_to_cht":"你已切换为繁体","cht_to_chs":"你已切换为简体","day_to_night":"你已切换为深色模式","night_to_day":"你已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#121212","position":"bottom-left"},
  source: {
    jQuery: 'https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js',
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/justifiedGallery/dist/js/jquery.justifiedGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/justifiedGallery/dist/css/justifiedGallery.min.css'
    },
    fancybox: {
      js: 'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js',
      css: 'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: true,
  isanchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = { 
  isPost: true,
  isHome: false,
  isHighlightShrink: true,
  isToc: true,
  postUpdate: '2021-05-12 00:00:00'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    })(window)</script><meta name="generator" content="Hexo 5.4.0"><link rel="alternate" href="/blog/atom.xml" title="Jeffery Blog" type="application/atom+xml">
</head><body><div id="loading-box"><div class="loading-left-bg"></div><div class="loading-right-bg"></div><img class="load-image" src= "" data-lazy-src="/blog/img/loading.gif" alt=""/></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="author-avatar"><img class="avatar-img" src= "" data-lazy-src="/blog/img/home/avatar.jpeg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="site-data"><div class="data-item is-center"><div class="data-item-link"><a href="/blog/archives/"><div class="headline">文章</div><div class="length-num">12</div></a></div></div><div class="data-item is-center"><div class="data-item-link"><a href="/blog/tags/"><div class="headline">标签</div><div class="length-num">6</div></a></div></div><div class="data-item is-center"><div class="data-item-link"><a href="/blog/categories/"><div class="headline">分类</div><div class="length-num">7</div></a></div></div></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/blog/"><i class="fa-fw fa fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/blog/archives/"><i class="fa-fw fa fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/blog/tags/"><i class="fa-fw fa fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/blog/categories/"><i class="fa-fw fa fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 媒体</span><i class="fas fa-chevron-down expand"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/blog/books/"><i class="fa-fw fas fa-book"></i><span> 阅读</span></a></li><li><a class="site-page child" href="/blog/music/"><i class="fa-fw fa fa-music"></i><span> 音乐</span></a></li><li><a class="site-page child" href="/blog/movies/"><i class="fa-fw fa fa-film"></i><span> 电影</span></a></li><li><a class="site-page child" href="/blog/gallery/"><i class="fa-fw far fa-image"></i><span> 相册</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/blog/box/"><i class="fa-fw fab fa-xbox"></i><span> 聚宝盒</span></a></div><div class="menus_item"><a class="site-page" href="/blog/talk/"><i class="fa-fw far fa-comment"></i><span> 微语</span></a></div><div class="menus_item"><a class="site-page" href="/blog/link/"><i class="fa-fw fa fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/blog/about/"><i class="fa-fw fa fa-heart"></i><span> 关于</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png')"><nav id="nav"><span id="blog_name"><a id="site-name" href="/blog/">Jeffery Blog</a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/blog/"><i class="fa-fw fa fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/blog/archives/"><i class="fa-fw fa fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/blog/tags/"><i class="fa-fw fa fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/blog/categories/"><i class="fa-fw fa fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 媒体</span><i class="fas fa-chevron-down expand"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/blog/books/"><i class="fa-fw fas fa-book"></i><span> 阅读</span></a></li><li><a class="site-page child" href="/blog/music/"><i class="fa-fw fa fa-music"></i><span> 音乐</span></a></li><li><a class="site-page child" href="/blog/movies/"><i class="fa-fw fa fa-film"></i><span> 电影</span></a></li><li><a class="site-page child" href="/blog/gallery/"><i class="fa-fw far fa-image"></i><span> 相册</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/blog/box/"><i class="fa-fw fab fa-xbox"></i><span> 聚宝盒</span></a></div><div class="menus_item"><a class="site-page" href="/blog/talk/"><i class="fa-fw far fa-comment"></i><span> 微语</span></a></div><div class="menus_item"><a class="site-page" href="/blog/link/"><i class="fa-fw fa fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/blog/about/"><i class="fa-fw fa fa-heart"></i><span> 关于</span></a></div></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">（Vue源码六）Vue 组件原理分析</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2021-05-03T16:00:00.000Z" title="发表于 2021-05-04 00:00:00">2021-05-04</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2021-05-11T16:00:00.000Z" title="更新于 2021-05-12 00:00:00">2021-05-12</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/blog/categories/%E5%89%8D%E7%AB%AF/">前端</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/blog/categories/%E5%89%8D%E7%AB%AF/vue/">vue</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/blog/categories/%E5%89%8D%E7%AB%AF/vue/vue-%E6%BA%90%E7%A0%81/">vue 源码</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">8.2k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>30分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="（Vue源码六）Vue 组件原理分析"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><h2 id="组件渲染流程图"><a href="#组件渲染流程图" class="headerlink" title="组件渲染流程图"></a>组件渲染流程图</h2><p><a data-fancybox target="_blank" rel="noopener" href="https://gitee.com/FIF/pic-beg/raw/master/images/logo/1620540437346-component-analyse.png"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/logo/1620540437346-component-analyse.png" alt="组件化源码分析"></a></p>
<h2 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h2><ul>
<li>全局注册组件和局部注册组件原理，它们之间的异同是什么？</li>
<li>组件是如何渲染成DOM？</li>
<li>组件的 <code>$root</code>, <code>$parent</code>, <code>$children</code> 是如何建立的层级关系？</li>
<li><code>mixins</code>, <code>extends</code> 定义的组件选项 <code>components</code> 是如何被使用的？</li>
<li>组件的两种使用场景：函数式组件和异步组件，它们的实现原理是什么，解决什么问题？</li>
</ul>
<p>带着这几个问题一起来分析 <code>Vue</code> 组件的实现原理。</p>
<h2 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h2><p>使用 <code>vue</code> 框架开发页面，其实本质上就是写一个个组件。独立的组件将页面结构化，开发者可以专注于某个功能的开发和扩展；同时组件的设计理念又使得功能具有复用性，不同的页面可以进行组件功能的共享。</p>
<p>了解组件注册的实现思路，分析组件渲染的流程；知道组件和组件之间是如何建立联系的，组件之间是如何通信的，组件的使用场景有哪些，掌握这些底层的实现思路对于在解决<code>Vue</code>组件相关问题上有明显的帮助。</p>
<h2 id="组件两种注册方式"><a href="#组件两种注册方式" class="headerlink" title="组件两种注册方式"></a>组件两种注册方式</h2><p><code>Vue</code> 组件在使用之前需要进行注册，而注册的方式有两种，全局注册和局部注册。在进入源码分析之前，先回忆一下两者的用法，以便后续掌握两者的差异。</p>
<h3 id="全局注册"><a href="#全局注册" class="headerlink" title="全局注册"></a>全局注册</h3><blockquote>
<p>Vue.component( id, [definition] )</p>
</blockquote>
<ul>
<li><p><strong>参数</strong>： </p>
<ul>
<li><code>&#123;string&#125; id</code></li>
<li><code>&#123;Function | Object&#125; [definition]</code></li>
</ul>
</li>
<li><p><strong>用法</strong>：</p>
<p>注册或获取全局组件。注册还会自动使用给定的 <code>id</code> 设置组件的名称</p>
</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 注册组件，传入一个扩展过的构造器</span></span><br><span class="line">Vue.component(<span class="string">&#x27;my-component&#x27;</span>, Vue.extend(&#123; <span class="comment">/* ... */</span> &#125;))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 注册组件，传入一个选项对象 (自动调用 Vue.extend)</span></span><br><span class="line">Vue.component(<span class="string">&#x27;my-component&#x27;</span>, &#123; <span class="comment">/* ... */</span> &#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 异步组件</span></span><br><span class="line">Vue.component(<span class="string">&#x27;my-component&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">resolve, reject</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">require</span>([./componentPath], resolve)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取注册的组件 (始终返回构造器)</span></span><br><span class="line"><span class="keyword">var</span> MyComponent = Vue.component(<span class="string">&#x27;my-component&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p><strong>全局注册的组件需要在全局实例化Vue前调用</strong>，注册之后可以用在任何新创建的<code>Vue</code>实例中调用。</p>
<h3 id="局部注册"><a href="#局部注册" class="headerlink" title="局部注册"></a>局部注册</h3><p>当只需要在某个局部用到某个组件时，可以使用局部注册的方式进行组件注册，此时局部注册的组件只能在注册该组件内部使用。</p>
<ul>
<li><strong>类型</strong>：<code>Object</code></li>
<li><strong>详细</strong>：</li>
</ul>
<p>包含 Vue 实例可用组件的哈希表。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> myTest = &#123;</span><br><span class="line">    template: <span class="string">&#x27;&lt;div&gt;&#123;&#123;test&#125;&#125;&lt;/div&gt;&#x27;</span>,</span><br><span class="line">    data () &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            test: <span class="string">&#x27;child&#x27;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> vm = <span class="keyword">new</span> Vue(&#123;</span><br><span class="line">    el: <span class="string">&#x27;#app&#x27;</span>,</span><br><span class="line">    component: &#123;</span><br><span class="line">        myTest</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>



<h3 id="注册过程分析"><a href="#注册过程分析" class="headerlink" title="注册过程分析"></a>注册过程分析</h3><p><strong>全局注册</strong></p>
<p>简单回顾组件的两种注册方式后，我们来看注册过程到底发生了什么，以全局组件注册为例。它通过<code>Vue.component(name, &#123;...&#125;)</code>进行组件注册，<code>Vue.component</code> 是在 <code>Vue</code> 源码引入阶段定义的静态方法。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 路径：src/core/global-api/index.js</span></span><br><span class="line"><span class="comment">// 初始化全局api</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">initGlobalAPI</span>(<span class="params">Vue</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  initAssetRegisters(Vue);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p><code>initAssetRegisters</code> 挂载全局方法 <code>component</code>, <code>filter</code>, <code>directive</code></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> ASSET_TYPES = [</span><br><span class="line">    <span class="string">&#x27;component&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;directive&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;filter&#x27;</span></span><br><span class="line">];</span><br><span class="line"></span><br><span class="line"><span class="comment">// src/core/global-api/assets.js</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">initAssetRegisters</span>(<span class="params">Vue</span>)</span>&#123;</span><br><span class="line">    <span class="comment">// 定义ASSET_TYPES中每个属性的方法，其中包括component</span></span><br><span class="line">    ASSET_TYPES.forEach(<span class="function"><span class="keyword">function</span> (<span class="params">type</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// type: component,directive,filter</span></span><br><span class="line">      Vue[type] = <span class="function"><span class="keyword">function</span> (<span class="params">id,definition</span>) </span>&#123;</span><br><span class="line">          <span class="keyword">if</span> (!definition) &#123;</span><br><span class="line">            <span class="comment">// 直接返回注册组件的构造函数</span></span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">this</span>.options[type + <span class="string">&#x27;s&#x27;</span>][id]</span><br><span class="line">          &#125;</span><br><span class="line">          <span class="keyword">if</span> (type === <span class="string">&#x27;component&#x27;</span>) &#123;</span><br><span class="line">            <span class="comment">// 验证component组件名字是否合法</span></span><br><span class="line">            validateComponentName(id);</span><br><span class="line">          &#125;</span><br><span class="line">          <span class="keyword">if</span> (type === <span class="string">&#x27;component&#x27;</span> &amp;&amp; isPlainObject(definition)) &#123;</span><br><span class="line">            <span class="comment">// 组件名称设置</span></span><br><span class="line">            definition.name = definition.name || id;</span><br><span class="line">            <span class="comment">// Vue.extend() 创建子组件，返回子类构造器</span></span><br><span class="line">            definition = <span class="built_in">this</span>.options._base.extend(definition);</span><br><span class="line">          &#125;</span><br><span class="line">          <span class="comment">// 为Vue.options 上的components属性添加将子类构造器</span></span><br><span class="line">          <span class="built_in">this</span>.options[type + <span class="string">&#x27;s&#x27;</span>][id] = definition;</span><br><span class="line">          <span class="keyword">return</span> definition</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>Vue.component</code>有两个参数，一个是需要注册组件的组件名，另一个是组件选项，如果第二个参数没有传递，则会直接返回注册过的组件选项。否则意味着需要对该组件进行注册，注册过程先会对组件名的合法性进行检测，要求组件名不允许出现非法的标签，包括<code>Vue</code>内置的组件名，如<code>slot, component</code>等。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">validateComponentName</span>(<span class="params">name</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="keyword">new</span> <span class="built_in">RegExp</span>((<span class="string">&quot;^[a-zA-Z][\\-\\.0-9_&quot;</span> + (unicodeRegExp.source) + <span class="string">&quot;]*$&quot;</span>)).test(name)) &#123;</span><br><span class="line">      <span class="comment">// 正则判断检测是否为非法的标签</span></span><br><span class="line">      warn(</span><br><span class="line">        <span class="string">&#x27;Invalid component name: &quot;&#x27;</span> + name + <span class="string">&#x27;&quot;. Component names &#x27;</span> +</span><br><span class="line">        <span class="string">&#x27;should conform to valid custom element name in html5 specification.&#x27;</span></span><br><span class="line">      );</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 不能使用Vue自身自定义的组件名，如slot, component,不能使用html的保留标签，如 h1, svg等</span></span><br><span class="line">    <span class="keyword">if</span> (isBuiltInTag(name) || config.isReservedTag(name)) &#123;</span><br><span class="line">      warn(</span><br><span class="line">        <span class="string">&#x27;Do not use built-in or reserved HTML elements as component &#x27;</span> +</span><br><span class="line">        <span class="string">&#x27;id: &#x27;</span> + name</span><br><span class="line">      );</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>组件名的合法性检测后，如果第二个参数是一个纯对象，调用 <code>extend</code> 方法，此时的<code>this.options._base</code>代表的就是<code>Vue</code>。</p>
<p><code>Vue.extend</code> 执行流程分析：</p>
<ul>
<li><code>Vue.extend</code> 方法基于<code>Vue</code>去创建一个子类，此时的父类是<code>Vue</code>，并且创建子类过程使用原型继承父类 <code>Vue</code> , 这时子组件就可以调用<code>Vue</code> 方法，比如 <code>$emit</code>, <code>$on</code> 等。</li>
<li><code>mergeOption</code> 将定义的选项和父类的选项进行合并，合并遵循合并策略，可以阅读 <a href="https://fif.gitee.io/blog/2021/04/23/vue/vue-code-5.mergeOptions/">Vue丰富的合并策略</a> 了解。</li>
<li>子类挂载 <code>extend, mixin, use, component, filter, directive</code> 静态方法</li>
<li>处理递归组件，最终返回一个子类构造器 <code>Sub</code>。</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">Vue.extend = <span class="function"><span class="keyword">function</span> (<span class="params">extendOptions: <span class="built_in">Object</span></span>): <span class="title">Function</span> </span>&#123;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">    <span class="keyword">const</span> name = extendOptions.name || Super.options.name</span><br><span class="line">    <span class="keyword">if</span> (process.env.NODE_ENV !== <span class="string">&#x27;production&#x27;</span> &amp;&amp; name) &#123;</span><br><span class="line">      validateComponentName(name) <span class="comment">// 检验组件名称</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> Sub = <span class="function"><span class="keyword">function</span> <span class="title">VueComponent</span> (<span class="params">options</span>) </span>&#123;</span><br><span class="line">      <span class="built_in">this</span>._init(options)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 原型继承</span></span><br><span class="line">    Sub.prototype = <span class="built_in">Object</span>.create(Super.prototype)</span><br><span class="line">    Sub.prototype.constructor = Sub</span><br><span class="line">    Sub.cid = cid++</span><br><span class="line">    <span class="comment">// 子类和父类构造器的配置选项进行合并</span></span><br><span class="line">    Sub.options = mergeOptions(</span><br><span class="line">      Super.options,</span><br><span class="line">      extendOptions</span><br><span class="line">    )</span><br><span class="line">    Sub[<span class="string">&#x27;super&#x27;</span>] = Super</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 继承父类方法</span></span><br><span class="line">    Sub.extend = Super.extend</span><br><span class="line">    Sub.mixin = Super.mixin</span><br><span class="line">    Sub.use = Super.use</span><br><span class="line"></span><br><span class="line">    ASSET_TYPES.forEach(<span class="function"><span class="keyword">function</span> (<span class="params">type</span>) </span>&#123;</span><br><span class="line">      Sub[type] = Super[type]</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="comment">// 递归组件</span></span><br><span class="line">    <span class="keyword">if</span> (name) &#123;</span><br><span class="line">      Sub.options.components[name] = Sub</span><br><span class="line">    &#125;</span><br><span class="line">		<span class="comment">// ...</span></span><br><span class="line">    <span class="keyword">return</span> Sub</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>全局注册组件就是 <code>Vue</code> 实例化前创建一个基于<code>Vue</code>的子类构造器，并将组件的信息加载到实例<code>options.components</code>对象中。</p>
<p><strong>局部注册</strong></p>
<blockquote>
<p>局部注册，本质上也是调用 <code>Vue.extend</code> 方法得到一个 <code>Vue</code> 子类构造器</p>
</blockquote>
<p>局部注册组件和全局注册组件的原理一样，区别在于：</p>
<ul>
<li><p>全局注册添加的子组件在根实例下；局部注册添加的对象配置是在某个组件下</p>
</li>
<li><p>全局注册添加的是一个函数；局部注册添加的是一个对象</p>
</li>
</ul>
<p>因此局部注册中缺少了一步构建子类构造器的过程，这个过程放在哪里进行呢？ 先看 <code>createComponent</code> 的源码，源码中根据选项是对象还是函数来区分局部和全局注册组件【异步组件除外】，<strong>如果选项的值是对象，则该组件是局部注册的组件，此时在创建子<code>Vnode</code>时会调用 父类的<code>extend</code>方法去创建一个子类构造器。</strong></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/core/vdom/create-component.js</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createComponent</span> (<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  Ctor: Class&lt;Component&gt; | <span class="built_in">Function</span> | <span class="built_in">Object</span> | <span class="keyword">void</span>, <span class="comment">// 子类构造器</span></span></span></span><br><span class="line"><span class="function"><span class="params">  data: ?VNodeData,</span></span></span><br><span class="line"><span class="function"><span class="params">  context: Component,</span></span></span><br><span class="line"><span class="function"><span class="params">  children: ?<span class="built_in">Array</span>&lt;VNode&gt;,</span></span></span><br><span class="line"><span class="function"><span class="params">  tag?: string <span class="comment">// 子组件占位符</span></span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">VNode</span> | <span class="title">Array</span>&lt;<span class="title">VNode</span>&gt; | <span class="title">void</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// _base存放了Vue,作为基类</span></span><br><span class="line">  <span class="keyword">const</span> baseCtor = contextß.$options._base</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 针对局部组件注册场景</span></span><br><span class="line">  <span class="keyword">if</span> (isObject(Ctor)) &#123;</span><br><span class="line">    Ctor = baseCtor.extend(Ctor)</span><br><span class="line">  &#125;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>从这里可以得出结论，不管是全局注册组件还是局部注册组件都要调用 <code>Vue.extend</code> 方法</p>
<h2 id="组件渲染流程"><a href="#组件渲染流程" class="headerlink" title="组件渲染流程"></a>组件渲染流程</h2><p>介绍完组件注册过程，接下来分析组件挂载和渲染过程。<code>new Vue</code> 初始化后，会执行 <code>$mount</code> 方法挂载，<code>runtime + compiler</code> 版本<code>$mount</code> 包含编译器将 <code>template</code> 转化为 <code>render</code> 函数。在实例化 <code>render Watcher</code> 调用 <code>updateComponent</code> ，执行 <code>render</code> 函数调用 <code>createElement</code> 得到 <code>vnode</code>, 最终组成 <code>vnode Tree</code> ，最后调用 <code>_update</code> 将 <code>vnode Tree</code> 转化为真实的 <code>DOM Tree</code></p>
<p><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/logo/1620539916261-image-20210509135812496.png" alt="image-20210509135812496"></p>
<h3 id="具体流程分析"><a href="#具体流程分析" class="headerlink" title="具体流程分析"></a>具体流程分析</h3><p>结合实际的例子对照着流程图分析一下这个过程：</p>
<ul>
<li><p>场景</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Vue.component(<span class="string">&#x27;test&#x27;</span>, &#123;</span><br><span class="line">template: <span class="string">&#x27;&lt;span&gt;hello world&lt;/span&gt;&#x27;</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> vm = <span class="keyword">new</span> Vue(&#123;</span><br><span class="line">el: <span class="string">&#x27;#app&#x27;</span>,</span><br><span class="line">template: <span class="string">&#x27;&lt;div&gt;&lt;test&gt;&lt;/test&gt;&lt;/div&gt;&#x27;</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure></li>
<li><p>父<code>render</code>函数</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"><span class="function"><span class="title">with</span>(<span class="params"><span class="built_in">this</span></span>)</span>&#123;<span class="keyword">return</span> _c(<span class="string">&#x27;div&#x27;</span>,[_c(<span class="string">&#x27;test&#x27;</span>)],<span class="number">1</span>)&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ul>
<ul>
<li><code>Vue</code>根实例初始化会执行 <code>vm.$mount(vm.$options.el)</code>实例挂载的过程，完整流程会经历<code>render</code>函数生成<code>Vnode</code>，以及<code>Vnode</code>生成真实<code>DOM</code>的过程。</li>
<li><code>render</code>函数生成<code>Vnode</code>过程中，子会优先父执行生成<code>Vnode</code>过程，也就是<code>_c(&#39;test&#39;)</code>函数会先被执行。<code>&#39;test&#39;</code>会先判断是普通的<code>html</code>标签还是组件的占位符。</li>
<li>如果为一般标签，会执行<code>new Vnode</code>过程；如果是组件的占位符，则会在判断组件已经被注册过的前提下进入<code>createComponent</code>创建子组件<code>Vnode</code>的过程。</li>
<li><code>createComponent</code>是创建组件<code>Vnode</code>的过程，创建过程会再次<strong>合并选项配置，并安装组件相关的内部钩子</strong>，最后通过<code>new Vnode()</code>生成以<code>vue-component</code>开头的<code>Virtual DOM</code></li>
<li><code>render</code>函数执行过程也是一个循环递归调用创建<code>Vnode</code>的过程，执行3，4步之后，完整的生成了一个包含各个子组件的<code>Vnode tree</code></li>
</ul>
<p><code>render</code> 函数执行，调用 <code>_createElement</code> 函数生成 <code>vnode</code></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 内部执行将render函数转化为Vnode的函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">_createElement</span>(<span class="params">context,tag,data,children,normalizationType</span>) </span>&#123;</span><br><span class="line">  ···</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> tag === <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">    <span class="comment">// 子节点的标签为普通的html标签，直接创建Vnode</span></span><br><span class="line">    <span class="keyword">if</span> (config.isReservedTag(tag)) &#123;</span><br><span class="line">      vnode = <span class="keyword">new</span> VNode(</span><br><span class="line">        config.parsePlatformTagName(tag), data, children,</span><br><span class="line">        <span class="literal">undefined</span>, <span class="literal">undefined</span>, context</span><br><span class="line">      );</span><br><span class="line">    <span class="comment">// 子节点标签为注册过的组件标签名，则子组件Vnode的创建过程</span></span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> ((!data || !data.pre) &amp;&amp; isDef(Ctor = resolveAsset(context.$options, <span class="string">&#x27;components&#x27;</span>, tag))) &#123;</span><br><span class="line">      <span class="comment">// 创建子组件Vnode</span></span><br><span class="line">      vnode = createComponent(Ctor, data, context, children, tag);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><code>config.isReservedTag(tag)</code>用来判断标签是否为普通的<code>html</code>标签，如果是普通节点会直接创建<code>Vnode</code>节点，如果不是，则需要判断这个占位符组件是否已经注册到，可以通过<code>context.$options.components[组件名]</code>拿到注册后的组件选项。如何判断组件是否已经全局注册，看看<code>resolveAsset</code>的实现。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 需要明确组件是否已经被注册</span></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">resolveAsset</span> (<span class="params">options,type,id,warnMissing</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 标签为字符串</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> id !== <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 这里是 options.component</span></span><br><span class="line">    <span class="keyword">var</span> assets = options[type];</span><br><span class="line">    <span class="comment">// 这里的分支分别支持大小写，驼峰的命名规范</span></span><br><span class="line">    <span class="keyword">if</span> (hasOwn(assets, id)) &#123; <span class="keyword">return</span> assets[id] &#125;</span><br><span class="line">    <span class="keyword">var</span> camelizedId = camelize(id);</span><br><span class="line">    <span class="comment">// 驼峰转化</span></span><br><span class="line">    <span class="keyword">if</span> (hasOwn(assets, camelizedId)) &#123; <span class="keyword">return</span> assets[camelizedId] &#125;</span><br><span class="line">    <span class="comment">// 首字符转化为大写</span></span><br><span class="line">    <span class="keyword">var</span> PascalCaseId = capitalize(camelizedId);</span><br><span class="line">    <span class="keyword">if</span> (hasOwn(assets, PascalCaseId)) &#123; <span class="keyword">return</span> assets[PascalCaseId] &#125;</span><br><span class="line">    <span class="comment">// fallback to prototype chain</span></span><br><span class="line">    <span class="keyword">var</span> res = assets[id] || assets[camelizedId] || assets[PascalCaseId];</span><br><span class="line">    <span class="keyword">if</span> (warnMissing &amp;&amp; !res) &#123;</span><br><span class="line">      warn(</span><br><span class="line">        <span class="string">&#x27;Failed to resolve &#x27;</span> + type.slice(<span class="number">0</span>, -<span class="number">1</span>) + <span class="string">&#x27;: &#x27;</span> + id,</span><br><span class="line">        options</span><br><span class="line">      );</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 最终返回子类的构造器</span></span><br><span class="line">    <span class="keyword">return</span> res</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>



<p>拿到注册过的子类构造器后，调用<code>createComponent</code>方法创建子组件<code>Vnode</code></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建子组件过程</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">createComponent</span> (<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">   Ctor, <span class="comment">// 子类构造器</span></span></span></span><br><span class="line"><span class="function"><span class="params">   data,</span></span></span><br><span class="line"><span class="function"><span class="params">   context, <span class="comment">// vm实例</span></span></span></span><br><span class="line"><span class="function"><span class="params">   children, <span class="comment">// 子节点</span></span></span></span><br><span class="line"><span class="function"><span class="params">   tag <span class="comment">// 子组件占位符</span></span></span></span><br><span class="line"><span class="function"><span class="params"> </span>) </span>&#123;</span><br><span class="line">   ···</span><br><span class="line">   <span class="comment">// Vue.options里的_base属性存储Vue构造器</span></span><br><span class="line">   <span class="keyword">var</span> baseCtor = context.$options._base;</span><br><span class="line">   <span class="comment">// 针对局部组件注册场景</span></span><br><span class="line">   <span class="keyword">if</span> (isObject(Ctor)) &#123;</span><br><span class="line">     Ctor = baseCtor.extend(Ctor);</span><br><span class="line">   &#125;</span><br><span class="line">   data = data || &#123;&#125;;</span><br><span class="line">   <span class="comment">// 构造器配置合并</span></span><br><span class="line">   resolveConstructorOptions(Ctor);</span><br><span class="line">   <span class="comment">// 挂载组件钩子</span></span><br><span class="line">   installComponentHooks(data);</span><br><span class="line">   <span class="comment">// return a placeholder vnode</span></span><br><span class="line">   <span class="keyword">var</span> name = Ctor.options.name || tag;</span><br><span class="line">   <span class="comment">// 创建子组件vnode，名称以 vue-component- 开头, children 为空</span></span><br><span class="line">   <span class="keyword">var</span> vnode = <span class="keyword">new</span> VNode((<span class="string">&quot;vue-component-&quot;</span> + (Ctor.cid) + (name ? (<span class="string">&quot;-&quot;</span> + name) : <span class="string">&#x27;&#x27;</span>)),data, <span class="literal">undefined</span>, <span class="literal">undefined</span>, <span class="literal">undefined</span>, context,&#123; <span class="attr">Ctor</span>: Ctor, <span class="attr">propsData</span>: propsData, <span class="attr">listeners</span>: listeners, <span class="attr">tag</span>: tag, <span class="attr">children</span>: children &#125;,asyncFactory);</span><br><span class="line">   <span class="keyword">return</span> vnode</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>



<p>其中关键是安装组件钩子函数，几个钩子函数会在后续<code>patch</code>过程的不同阶段执行，这里看看<code>installComponentHooks</code>安装组件钩子函数时做了哪些操作。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">  <span class="comment">// 组件内部自带钩子</span></span><br><span class="line"> <span class="keyword">var</span> componentVNodeHooks = &#123;</span><br><span class="line">    init (vnode, hydrating) &#123;</span><br><span class="line">    &#125;,</span><br><span class="line">    prepatch (oldVnode, vnode) &#123;</span><br><span class="line">    &#125;,</span><br><span class="line">    insert (vnode) &#123;</span><br><span class="line">    &#125;,</span><br><span class="line">    destroy (vnode) &#123;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line"><span class="keyword">var</span> hooksToMerge = <span class="built_in">Object</span>.keys(componentVNodeHooks);</span><br><span class="line"><span class="comment">// 将componentVNodeHooks 钩子函数合并到组件data.hook中 </span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">installComponentHooks</span> (<span class="params">data</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> hooks = data.hook || (data.hook = &#123;&#125;);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; hooksToMerge.length; i++) &#123;</span><br><span class="line">      <span class="keyword">var</span> key = hooksToMerge[i];</span><br><span class="line">      <span class="keyword">var</span> existing = hooks[key];</span><br><span class="line">      <span class="keyword">var</span> toMerge = componentVNodeHooks[key];</span><br><span class="line">      <span class="comment">// 如果钩子函数存在，则执行mergeHook$1方法合并</span></span><br><span class="line">      <span class="keyword">if</span> (existing !== toMerge &amp;&amp; !(existing &amp;&amp; existing._merged)) &#123;</span><br><span class="line">        hooks[key] = existing ? mergeHook$<span class="number">1</span>(toMerge, existing) : toMerge;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">mergeHook$1</span> (<span class="params">f1, f2</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 返回一个依次执行f1,f2的函数</span></span><br><span class="line">    <span class="keyword">var</span> merged = <span class="function"><span class="keyword">function</span> (<span class="params">a, b</span>) </span>&#123;</span><br><span class="line">      f1(a, b);</span><br><span class="line">      f2(a, b);</span><br><span class="line">    &#125;;</span><br><span class="line">    merged._merged = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">return</span> merged</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>



<h2 id="组件-Vnode-渲染真实-DOM"><a href="#组件-Vnode-渲染真实-DOM" class="headerlink" title="组件 Vnode 渲染真实 DOM"></a>组件 Vnode 渲染真实 DOM</h2><h3 id="具体流程分析-1"><a href="#具体流程分析-1" class="headerlink" title="具体流程分析"></a>具体流程分析</h3><ul>
<li><p>经过<code>vm._render()</code>生成完整的<code>Virtual Dom</code>树后，紧接着执行<code>Vnode</code>渲染真实<code>DOM</code>的过程，这个过程是<code>vm._update()</code>方法的执行，而其核心是<code>vm.__patch__</code>。</p>
</li>
<li><p><code>vm.__patch__</code>内部会通过 <code>createElm</code>去创建真实的<code>DOM</code>元素，期间遇到子<code>Vnode</code>递归调用<code>createElm</code>方法。</p>
</li>
<li><p>递归调用过程中，通过<code>createComponent</code>方法创建组件，如果是组件重新走实例化组件和挂载，该方法和渲染<code>Vnode</code>阶段的方法<code>createComponent</code>不同，他会调用子组件的<code>init</code>初始化钩子函数，并完成组件的<code>DOM</code>插入。</p>
</li>
<li><p><code>init</code>初始化组件钩子函数的核心是实例化子组件并进行挂载，实例化子组件的过程又回到合并配置，初始化生命周期，初始化事件中心，初始化渲染的过程。初始化后手动挂载<code>$mount</code>。</p>
</li>
</ul>
<p>完成所有子组件的实例化和节点挂载后，最后才回到根节点的挂载。</p>
<p><code>__patch__</code>核心代码是通过<code>createElm</code>创建真实节点，当创建过程中遇到子<code>vnode</code>时，会调用<code>createChildren</code>,<code>createChildren</code>的目的是对子<code>vnode</code>递归调用<code>createElm</code>创建子组件节点。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建真实dom</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createElm</span> (<span class="params">vnode,insertedVnodeQueue,parentElm,refElm,nested,ownerArray,index</span>) </span>&#123;</span><br><span class="line">  ···</span><br><span class="line">  <span class="comment">// 递归创建子组件真实节点,直到完成所有子组件的渲染才进行根节点的真实节点插入</span></span><br><span class="line">  <span class="keyword">if</span> (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line">  ···</span><br><span class="line">  <span class="keyword">var</span> children = vnode.children;</span><br><span class="line">  <span class="comment">// </span></span><br><span class="line">  createChildren(vnode, children, insertedVnodeQueue);</span><br><span class="line">  ···</span><br><span class="line">  insert(parentElm, vnode.elm, refElm);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createChildren</span>(<span class="params">vnode, children, insertedVnodeQueue</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; children.length; ++i) &#123;</span><br><span class="line">    <span class="comment">// 遍历子节点，递归调用创建真实dom节点的方法 - createElm</span></span><br><span class="line">    createElm(children[i], insertedVnodeQueue, vnode.elm, <span class="literal">null</span>, <span class="literal">true</span>, children, i);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>createComponent</code>方法会对子组件<code>Vnode</code>进行处理中，还记得在<code>Vnode</code>生成阶段为子<code>Vnode</code>安装了一系列的钩子函数吗，在这个步骤我们可以通过是否拥有这些定义好的钩子来判断是否是已经注册过的子组件，如果条件满足，则执行组件的<code>init</code>钩子。</p>
<p><code>init</code>钩子做的事情只有两个，<strong>实例化组件构造器，执行子组件的挂载流程。</strong></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createComponent</span> (<span class="params">vnode, insertedVnodeQueue, parentElm, refElm</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> i = vnode.data;</span><br><span class="line">  <span class="comment">// 是否有钩子函数可以作为判断是否为组件的唯一条件</span></span><br><span class="line">  <span class="keyword">if</span> (isDef(i = i.hook) &amp;&amp; isDef(i = i.init)) &#123;</span><br><span class="line">    <span class="comment">// 执行init钩子函数</span></span><br><span class="line">    i(vnode, <span class="literal">false</span> <span class="comment">/* hydrating */</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  ···</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> componentVNodeHooks = &#123;</span><br><span class="line">  init () &#123;</span><br><span class="line">    <span class="comment">// 忽略keepAlive过程</span></span><br><span class="line">    <span class="comment">// 实例化</span></span><br><span class="line">    <span class="keyword">var</span> child = vnode.componentInstance = createComponentInstanceForVnode(vnode,activeInstance);</span><br><span class="line">    <span class="comment">// 挂载</span></span><br><span class="line">    child.$mount(hydrating ? vnode.elm : <span class="literal">undefined</span>, hydrating);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createComponentInstanceForVnode</span>(<span class="params">vnode, parent</span>) </span>&#123;</span><br><span class="line">  ···</span><br><span class="line">  <span class="comment">// 实例化Vue子组件实例</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> vnode.componentOptions.Ctor(options)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>显然<code>Vnode</code>生成真实<code>DOM</code>的过程也是一个不断递归创建子节点的过程，<code>patch</code>过程如果遇到子<code>Vnode</code>,会优先实例化子组件，并且执行子组件的挂载流程，而挂载流程又会回到<code>_render,_update</code>的过程。在所有的子<code>Vnode</code>递归挂载后，最终才会真正挂载根节点。</p>
<h2 id="建立组件关联"><a href="#建立组件关联" class="headerlink" title="建立组件关联"></a>建立组件关联</h2><p>日常开发中，我们可以通过<code>vm.$parent</code>拿到父实例，也可以在父实例中通过<code>vm.$children</code>拿到实例中的子组件。显然，<code>Vue</code>在组件和组件之间建立了一层关联。接下来的内容，我们将探索如何建立组件之间的联系。</p>
<p>不管是父实例还是子实例，在初始化实例阶段执行 <code>_init</code> 有一个 <code>initLifecycle</code>的 过程。这个过程会<strong>把当前实例添加到父实例的<code>$children</code>属性中，并设置自身的<code>$parent</code>属性指向父实例。</strong>源码解析如下:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">initLifecycle</span> (<span class="params">vm</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> options = vm.$options;</span><br><span class="line">    <span class="comment">// 子组件注册时，会把父组件的实例挂载到自身选项的parent上</span></span><br><span class="line">    <span class="keyword">var</span> parent = options.parent;</span><br><span class="line">    <span class="comment">// 如果是子组件，并且该组件不是抽象组件时，将该组件的实例添加到父组件的$parent属性上，如果父组件是抽象组件，则一直往上层寻找，直到该父级组件不是抽象组件，并将，将该组件的实例添加到父组件的$parent属性</span></span><br><span class="line">    <span class="keyword">if</span> (parent &amp;&amp; !options.abstract) &#123;</span><br><span class="line">        <span class="keyword">while</span> (parent.$options.abstract &amp;&amp; parent.$parent) &#123;</span><br><span class="line">        parent = parent.$parent;</span><br><span class="line">        &#125;</span><br><span class="line">        parent.$children.push(vm);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 将自身的$parent属性指向父实例。</span></span><br><span class="line">    vm.$parent = parent;</span><br><span class="line">    vm.$root = parent ? parent.$root : vm;</span><br><span class="line">    vm.$children = [];</span><br><span class="line">    vm.$refs = &#123;&#125;;</span><br><span class="line">    vm._watcher = <span class="literal">null</span>;</span><br><span class="line">    vm._inactive = <span class="literal">null</span>;</span><br><span class="line">    vm._directInactive = <span class="literal">false</span>;</span><br><span class="line">    <span class="comment">// 该实例是否挂载</span></span><br><span class="line">    vm._isMounted = <span class="literal">false</span>;</span><br><span class="line">    <span class="comment">// 该实例是否被销毁</span></span><br><span class="line">    vm._isDestroyed = <span class="literal">false</span>;</span><br><span class="line">    <span class="comment">// 该实例是否正在被销毁</span></span><br><span class="line">    vm._isBeingDestroyed = <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>parent.$options.abstract</code> 是抽象组件的判断，在<code>vue</code>中有很多内置的抽象组件，例如<code>&lt;keep-alive&gt;&lt;/keep-alive&gt;,&lt;transition&gt;&lt;transition&gt;</code>等，这些抽象组件并不会出现在子父级的路径上，并且它们也不会参与<code>DOM</code>的渲染。</p>
<h2 id="函数式组件"><a href="#函数式组件" class="headerlink" title="函数式组件"></a>函数式组件</h2><p>组件除了常用的使用场景外，还提供了另外两种使用方式，分别是函数式组件和异步组件，函数式组件逻辑相对简单些，先来看看函数式组件。</p>
<p>函数式组件顾名思义可以理解为它是一个函数，有自己的作用域，可以实现复用，所以函数组件是无状态、无实例的。从原理上说，一般子组件都会经过实例化的过程，而单纯的函数组件并没有这个过程，它可以简单理解为一个中间层，只处理逻辑数据，不创建实例，也是由于这个行为，它的渲染开销会低很多。</p>
<p><strong>函数组件实际的应用场景</strong>，当我们需要在多个组件中选择一个来代为渲染，或者在将<code>children,props,data</code>等数据传递给子组件前进行数据处理时，我们都可以用函数式组件来完成，它本质上也是对组件的一个外部包装。</p>
<h3 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h3><p>定义两个组件对象，<code>test1，test2</code></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> test1 = &#123;</span><br><span class="line">props: [<span class="string">&#x27;msg&#x27;</span>],</span><br><span class="line">render: <span class="function"><span class="keyword">function</span> (<span class="params">createElement, context</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> createElement(<span class="string">&#x27;h1&#x27;</span>, <span class="built_in">this</span>.msg)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> test2 = &#123;</span><br><span class="line">props: [<span class="string">&#x27;msg&#x27;</span>],</span><br><span class="line">render: <span class="function"><span class="keyword">function</span> (<span class="params">createElement, context</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> createElement(<span class="string">&#x27;h2&#x27;</span>, <span class="built_in">this</span>.msg)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>定义一个函数式组件，它会根据计算结果选择其中一个组件进行选项</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Vue.component(<span class="string">&#x27;test3&#x27;</span>, &#123;</span><br><span class="line"><span class="comment">// 函数式组件的标志 functional设置为true</span></span><br><span class="line">functional: <span class="literal">true</span>,</span><br><span class="line">props: [<span class="string">&#x27;msg&#x27;</span>],</span><br><span class="line">render: <span class="function"><span class="keyword">function</span> (<span class="params">createElement, context</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> get = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> test1</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> createElement(get(), context)</span><br><span class="line">&#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<ul>
<li>函数式组件的使用</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&lt;test3 :msg=<span class="string">&quot;msg&quot;</span> id=<span class="string">&quot;test&quot;</span>&gt;</span><br><span class="line">&lt;/test3&gt;</span><br><span class="line"><span class="keyword">new</span> Vue(&#123;</span><br><span class="line">el: <span class="string">&#x27;#app&#x27;</span>,</span><br><span class="line">data: &#123;</span><br><span class="line">  msg: <span class="string">&#x27;test&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<ul>
<li>最终渲染的结果为：</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;h2&gt;test&lt;/h2&gt;</span><br></pre></td></tr></table></figure>



<h3 id="函数组件源码分析"><a href="#函数组件源码分析" class="headerlink" title="函数组件源码分析"></a>函数组件源码分析</h3><p>函数式组件会在组件的对象定义中，将<code>functional</code>属性设置为<code>true</code>，这个属性是区别普通组件和函数式组件的关键。同样的在遇到子组件占位符时，会进入<code>createComponent</code>进行子组件<code>Vnode</code>的创建。<strong>由于<code>functional</code>属性的存在，代码会进入函数式组件的分支中，并返回<code>createFunctionalComponent</code>调用的结果。</strong>注意，执行完<code>createFunctionalComponent</code>后，后续创建子<code>Vnode</code>的逻辑不会执行，这也是之后在创建真实节点过程中不会有子<code>Vnode</code>去实例化子组件的原因</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createComponent</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">  ···</span><br><span class="line">  <span class="keyword">if</span> (isTrue(Ctor.options.functional)) &#123;</span><br><span class="line">    <span class="keyword">return</span> createFunctionalComponent(Ctor, propsData, data, context, children)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>createFunctionalComponent</code>方法会对传入的数据进行检测和合并，实例化<code>FunctionalRenderContext</code>，最终调用函数式组件自定义的<code>render</code>方法执行渲染过程。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createFunctionalComponent</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  Ctor, <span class="comment">// 函数式组件构造器</span></span></span></span><br><span class="line"><span class="function"><span class="params">  propsData, <span class="comment">// 传入组件的props</span></span></span></span><br><span class="line"><span class="function"><span class="params">  data, <span class="comment">// 占位符组件传入的attr属性</span></span></span></span><br><span class="line"><span class="function"><span class="params">  context, <span class="comment">// vue实例</span></span></span></span><br><span class="line"><span class="function"><span class="params">  children<span class="comment">// 子节点</span></span></span></span><br><span class="line"><span class="function"><span class="params"></span>)</span>&#123;</span><br><span class="line">  <span class="comment">// 数据检测合并</span></span><br><span class="line">  <span class="keyword">var</span> options = Ctor.options;</span><br><span class="line">  <span class="keyword">var</span> props = &#123;&#125;;</span><br><span class="line">  <span class="keyword">var</span> propOptions = options.props;</span><br><span class="line">  <span class="keyword">if</span> (isDef(propOptions)) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> propOptions) &#123;</span><br><span class="line">      props[key] = validateProp(key, propOptions, propsData || emptyObject);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 合并attrs</span></span><br><span class="line">    <span class="keyword">if</span> (isDef(data.attrs)) &#123; mergeProps(props, data.attrs); &#125;</span><br><span class="line">    <span class="comment">// 合并props</span></span><br><span class="line">    <span class="keyword">if</span> (isDef(data.props)) &#123; mergeProps(props, data.props); &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">var</span> renderContext = <span class="keyword">new</span> FunctionalRenderContext(data,props,children,contextVm,Ctor);</span><br><span class="line">  <span class="comment">// 调用函数式组件中自定的render函数</span></span><br><span class="line">  <span class="keyword">var</span> vnode = options.render.call(<span class="literal">null</span>, renderContext._c, renderContext)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>而<code>FunctionalRenderContext</code>这个类最终的目的是定义一个和真实组件渲染不同的<code>render</code>方法。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">FunctionalRenderContext</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 省略其他逻辑</span></span><br><span class="line">  <span class="built_in">this</span>._c = <span class="function"><span class="keyword">function</span> (<span class="params">a, b, c, d</span>) </span>&#123; <span class="keyword">return</span> createElement(contextVm, a, b, c, d, needNormalization); &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>执行<code>render</code>函数的过程，又会递归调用<code>createElement</code>的方法，这时的组件已经是真实的组件，开始执行正常的组件挂载流程。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">options.render.call(<span class="literal">null</span>, renderContext._c, renderContext)</span><br></pre></td></tr></table></figure>

<p>我们知道函数式组件的 <code>render</code> 函数可以接收第二个参数，它的值就是上面的 <code>renderContext</code></p>
<p>问题：为什么函数式组件需要定义一个不同的<code>createElement</code>方法？</p>
<ul>
<li>函数式组件<code>createElement</code>和以往唯一的不同是，最后一个参数的不同，<code>createElement</code>会根据最后一个参数决定是否对子<code>Vnode</code>进行拍平，一般情况下，<code>children</code>编译生成结果都是<code>Vnode</code>类型，只有函数式组件比较特殊，它可以返回一个数组，这时候拍平就是有必要的。我们看下面的例子：</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">Vue.component(<span class="string">&#x27;test&#x27;</span>, &#123;  </span><br><span class="line">  functional: <span class="literal">true</span>,  </span><br><span class="line">  render: <span class="function"><span class="keyword">function</span> (<span class="params">createElement, context</span>) </span>&#123;  </span><br><span class="line">    <span class="keyword">return</span> context.slots().default  </span><br><span class="line">  &#125;  </span><br><span class="line">&#125;) </span><br><span class="line">&lt;test&gt; </span><br><span class="line">     &lt;p&gt;slot1&lt;/p&gt; </span><br><span class="line">     &lt;p&gt;slot&lt;/p&gt; </span><br><span class="line">&lt;/test&gt;</span><br></pre></td></tr></table></figure>

<p>此时函数式组件<code>test</code>的<code>render</code>函数返回的是两个<code>slot</code>的<code>Vnode</code>，它是以数组的形式存在的,这就是需要拍平的场景。</p>
<p>简单总结一下函数式组件，从源码中可以看出，函数式组件并不会像普通组件那样有实例化组件的过程，因此包括组件的生命周期，组件的数据管理这些过程都没有，它只会原封不动的接收传递给组件的数据做处理，并渲染需要的内容。因此作为纯粹的函数可以也大大降低渲染的开销。</p>
<h2 id="异步组件"><a href="#异步组件" class="headerlink" title="异步组件"></a>异步组件</h2><p>异步组件的使用场景通常定义在路由文件中，<code>webpack</code>遇到异步组件，会将其从主脚本中分离，减少脚本体积，实现<strong>按需加载</strong>，加快首屏加载时间</p>
<h3 id="工厂函数"><a href="#工厂函数" class="headerlink" title="工厂函数"></a>工厂函数</h3><p><code>Vue</code>中允许用户通过工厂函数的形式定义组件，这个工厂函数会异步解析组件定义，组件需要渲染的时候才会触发该工厂函数，加载结果会进行缓存，以供下一次调用组件时使用。具体使用：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 全局注册：</span></span><br><span class="line">Vue.component(<span class="string">&#x27;asyncComponent&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">resolve, reject</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">require</span>([<span class="string">&#x27;./test.vue&#x27;</span>], resolve)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 局部注册：</span></span><br><span class="line"><span class="keyword">var</span> vm = <span class="keyword">new</span> Vue(&#123;</span><br><span class="line">  el: <span class="string">&#x27;#app&#x27;</span>,</span><br><span class="line">  template: <span class="string">&#x27;&lt;div id=&quot;app&quot;&gt;&lt;asyncComponent&gt;&lt;/asyncComponent&gt;&lt;/div&gt;&#x27;</span>,</span><br><span class="line">  components: &#123;</span><br><span class="line">    asyncComponent: <span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> <span class="built_in">require</span>([<span class="string">&#x27;./test.vue&#x27;</span>], resolve),</span><br><span class="line">    <span class="comment">// 另外写法</span></span><br><span class="line">    asyncComponent: <span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&#x27;./test.vue&#x27;</span>),</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>



<h3 id="异步组件流程分析"><a href="#异步组件流程分析" class="headerlink" title="异步组件流程分析"></a>异步组件流程分析</h3><p>在上面组件挂载流程中，实例的挂载流程会创建<code>Vnode</code>过程，如果遇到子的占位符节点会调用<code>creatComponent</code>,而异步组件的处理逻辑也是在这个阶段处理。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建子组件过程</span></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">createComponent</span> (<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">    Ctor, <span class="comment">// 子类构造器</span></span></span></span><br><span class="line"><span class="function"><span class="params">    data,</span></span></span><br><span class="line"><span class="function"><span class="params">    context, <span class="comment">// vm实例</span></span></span></span><br><span class="line"><span class="function"><span class="params">    children, <span class="comment">// 子节点</span></span></span></span><br><span class="line"><span class="function"><span class="params">    tag <span class="comment">// 子组件占位符</span></span></span></span><br><span class="line"><span class="function"><span class="params">  </span>) </span>&#123;</span><br><span class="line">    ···</span><br><span class="line">    <span class="comment">// 针对局部注册组件创建子类构造器</span></span><br><span class="line">    <span class="keyword">if</span> (isObject(Ctor)) &#123;</span><br><span class="line">      Ctor = baseCtor.extend(Ctor);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 异步组件分支</span></span><br><span class="line">    <span class="keyword">var</span> asyncFactory;</span><br><span class="line">    <span class="keyword">if</span> (isUndef(Ctor.cid)) &#123;</span><br><span class="line">      <span class="comment">// 异步工厂函数</span></span><br><span class="line">      asyncFactory = Ctor;</span><br><span class="line">      <span class="comment">// 创建异步组件函数</span></span><br><span class="line">      Ctor = resolveAsyncComponent(asyncFactory, baseCtor);</span><br><span class="line">      <span class="keyword">if</span> (Ctor === <span class="literal">undefined</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> createAsyncPlaceholder(</span><br><span class="line">          asyncFactory,</span><br><span class="line">          data,</span><br><span class="line">          context,</span><br><span class="line">          children,</span><br><span class="line">          tag</span><br><span class="line">        )</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    ···</span><br><span class="line">    <span class="comment">// 创建子组件vnode</span></span><br><span class="line">    <span class="keyword">var</span> vnode = <span class="keyword">new</span> VNode(</span><br><span class="line">      (<span class="string">&quot;vue-component-&quot;</span> + (Ctor.cid) + (name ? (<span class="string">&quot;-&quot;</span> + name) : <span class="string">&#x27;&#x27;</span>)),</span><br><span class="line">      data, <span class="literal">undefined</span>, <span class="literal">undefined</span>, <span class="literal">undefined</span>, context,</span><br><span class="line">      &#123; <span class="attr">Ctor</span>: Ctor, <span class="attr">propsData</span>: propsData, <span class="attr">listeners</span>: listeners, <span class="attr">tag</span>: tag, <span class="attr">children</span>: children &#125;,</span><br><span class="line">      asyncFactory</span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">return</span> vnode</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p><strong>工厂函数的用法使得<code>Vue.component(name, options)</code>的第二个参数不是一个对象，因此不论是全局注册还是局部注册，都不会执行<code>Vue.extend</code>生成一个子组件的构造器，</strong>所以<code>Ctor.cid</code>不会存在，代码会进入异步组件的分支。</p>
<p>异步组件分支的核心是<code>resolveAsyncComponent</code>,它的处理逻辑分支众多，我们先关心工厂函数处理部分。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">resolveAsyncComponent</span> (<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">    factory,</span></span></span><br><span class="line"><span class="function"><span class="params">    baseCtor</span></span></span><br><span class="line"><span class="function"><span class="params">  </span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!isDef(factory.owners)) &#123;</span><br><span class="line">      <span class="comment">// 异步请求成功处理</span></span><br><span class="line">      <span class="keyword">var</span> resolve = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;&#125;</span><br><span class="line">      <span class="comment">// 异步请求失败处理</span></span><br><span class="line">      <span class="keyword">var</span> reject = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;&#125;</span><br><span class="line">      <span class="comment">// 创建子组件时会先执行工厂函数，并将resolve和reject传入</span></span><br><span class="line">      <span class="keyword">var</span> res = factory(resolve, reject);</span><br><span class="line">      <span class="comment">// resolved 同步返回</span></span><br><span class="line">      <span class="keyword">return</span> factory.loading</span><br><span class="line">        ? factory.loadingComp</span><br><span class="line">        : factory.resolved</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>针对异步组件工厂函数的写法，大致可以总结出以下三个步骤：</p>
<ol>
<li>定义异步请求成功的函数处理，定义异步请求失败的函数处理；</li>
<li>执行组件定义的工厂函数；</li>
<li>同步返回请求成功的函数处理。</li>
</ol>
<p><code>resolve, reject</code>的实现，都是<code>once</code>方法执行的结果，所以我们先关注一下高级函数<code>once</code>的原理。<strong>为了防止当多个地方调用异步组件时，<code>resolve,reject</code>不会重复执行，<code>once</code>函数保证了函数在代码只执行一次。也就是说，<code>once</code>缓存了已经请求过的异步组件</strong></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// once函数保证了这个调用函数只在系统中调用一次</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">once</span> (<span class="params">fn</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 利用闭包特性将called作为标志位</span></span><br><span class="line">  <span class="keyword">var</span> called = <span class="literal">false</span>;</span><br><span class="line">  <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 调用过则不再调用</span></span><br><span class="line">    <span class="keyword">if</span> (!called) &#123;</span><br><span class="line">      called = <span class="literal">true</span>;</span><br><span class="line">      fn.apply(<span class="built_in">this</span>, <span class="built_in">arguments</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>成功<code>resolve</code>和失败<code>reject</code>的详细处理逻辑如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 成功处理</span></span><br><span class="line"><span class="keyword">var</span> resolve = once(<span class="function"><span class="keyword">function</span> (<span class="params">res</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 转成组件构造器，并将其缓存到resolved属性中。</span></span><br><span class="line">  factory.resolved = ensureCtor(res, baseCtor);</span><br><span class="line">  <span class="keyword">if</span> (!sync) &#123;</span><br><span class="line">    <span class="comment">//强制更新渲染视图</span></span><br><span class="line">    forceRender(<span class="literal">true</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    owners.length = <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 失败处理</span></span><br><span class="line"><span class="keyword">var</span> reject = once(<span class="function"><span class="keyword">function</span> (<span class="params">reason</span>) </span>&#123;</span><br><span class="line">  warn(</span><br><span class="line">    <span class="string">&quot;Failed to resolve async component: &quot;</span> + (<span class="built_in">String</span>(factory)) +</span><br><span class="line">    (reason ? (<span class="string">&quot;\nReason: &quot;</span> + reason) : <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">  );</span><br><span class="line">  <span class="keyword">if</span> (isDef(factory.errorComp)) &#123;</span><br><span class="line">    factory.error = <span class="literal">true</span>;</span><br><span class="line">    forceRender(<span class="literal">true</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>异步组件加载完毕，会调用<code>resolve</code>定义的方法，方法会通过<code>ensureCtor</code>将加载完成的组件转换为组件构造器，并存储在<code>resolved</code>属性中，其中 <code>ensureCtor</code>的定义为：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">ensureCtor</span> (<span class="params">comp, base</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (comp.__esModule ||(hasSymbol &amp;&amp; comp[<span class="built_in">Symbol</span>.toStringTag] === <span class="string">&#x27;Module&#x27;</span>)) &#123;</span><br><span class="line">      comp = comp.default;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// comp结果为对象时，调用extend方法创建一个子类构造器</span></span><br><span class="line">    <span class="keyword">return</span> isObject(comp)</span><br><span class="line">      ? base.extend(comp)</span><br><span class="line">      : comp</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>组件构造器创建完毕，会进行一次视图的重新渲染，<strong>由于<code>Vue</code>是数据驱动视图渲染的，而组件在加载到完毕的过程中，并没有数据发生变化，因此需要手动强制更新视图。</strong><code>forceRender</code> 函数的内部会拿到每个调用异步组件的实例，执行原型上的<code>$forceUpdate</code>方法，更新组件</p>
<p>异步组件加载失败后，会调用<code>reject</code>定义的方法，方法会提示并标记错误，最后同样会强制更新视图。</p>
<p>回到异步组件创建的流程，执行异步过程会同步为加载中的异步组件创建一个注释节点<code>Vnode</code></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createComponent</span> (<span class="params"></span>)</span>&#123;</span><br><span class="line">  ···</span><br><span class="line">  <span class="comment">// 创建异步组件函数</span></span><br><span class="line">  Ctor = resolveAsyncComponent(asyncFactory, baseCtor);</span><br><span class="line">  <span class="keyword">if</span> (Ctor === <span class="literal">undefined</span>) &#123;</span><br><span class="line">    <span class="comment">// 创建注释节点</span></span><br><span class="line">    <span class="keyword">return</span> createAsyncPlaceholder(asyncFactory,data,context,children,tag)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>createAsyncPlaceholder</code>的定义也很简单,其中<code>createEmptyVNode</code>创建一个注释节点<code>vnode</code>，而<code>asyncFactory,asyncMeta</code>都是用来标注该节点为异步组件的临时节点和相关属性。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建注释Vnode</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createAsyncPlaceholder</span> (<span class="params">factory,data,context,children,tag</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> node = createEmptyVNode();</span><br><span class="line">  node.asyncFactory = factory;</span><br><span class="line">  node.asyncMeta = &#123; <span class="attr">data</span>: data, <span class="attr">context</span>: context, <span class="attr">children</span>: children, <span class="attr">tag</span>: tag &#125;;</span><br><span class="line">  <span class="keyword">return</span> node</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>执行<code>forceRender</code>触发组件的重新渲染过程时，又会再次调用<code>resolveAsyncComponent</code>,这时返回值<code>Ctor</code>不再为 <code>undefined</code>了，因此会正常走组件的<code>render,patch</code>过程。这时，旧的注释节点也会被取代。</p>
<h3 id="Promise异步组件"><a href="#Promise异步组件" class="headerlink" title="Promise异步组件"></a>Promise异步组件</h3><p>异步组件的第二种写法是在工厂函数中返回一个<code>promise</code>对象，我们知道<code>import</code>是<code>es6</code>引入模块加载的用法，但是<code>import</code>是一个静态加载的方法，它会优先模块内的其他语句执行。因此引入了<code>import()</code>,<code>import()</code>是一个运行时加载模块的方法，可以用来类比<code>require()</code>方法，区别在于前者是一个异步方法，后者是同步的，且<code>import()</code>会返回一个<code>promise</code>对象。</p>
<p>具体用法：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Vue.component(<span class="string">&#x27;asyncComponent&#x27;</span>, <span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&#x27;./test.vue&#x27;</span>))</span><br></pre></td></tr></table></figure>

<p>源码依然走着异步组件处理分支，并且大部分的处理过程还是工厂函数的逻辑处理，区别在于执行异步函数后会返回一个<code>promise</code>对象，成功加载则执行<code>resolve</code>,失败加载则执行<code>reject</code></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> res = factory(resolve, reject);</span><br><span class="line"><span class="comment">// res是返回的promise</span></span><br><span class="line"><span class="keyword">if</span> (isObject(res)) &#123;</span><br><span class="line">  <span class="keyword">if</span> (isPromise(res)) &#123;</span><br><span class="line">    <span class="keyword">if</span> (isUndef(factory.resolved)) &#123;</span><br><span class="line">      <span class="comment">// 核心处理</span></span><br><span class="line">      res.then(resolve, reject);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其中<code>promise</code>对象的判断最简单的是判断是否有<code>then</code>和<code>catch</code>方法：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 判断promise对象的方法</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">isPromise</span> (<span class="params">val</span>) </span>&#123;</span><br><span class="line">   <span class="keyword">return</span> (isDef(val) &amp;&amp; <span class="keyword">typeof</span> val.then === <span class="string">&#x27;function&#x27;</span> &amp;&amp; <span class="keyword">typeof</span> val.catch === <span class="string">&#x27;function&#x27;</span>)</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>



<h3 id="高级异步组件"><a href="#高级异步组件" class="headerlink" title="高级异步组件"></a>高级异步组件</h3><p>为了在操作上更加灵活，比如使用<code>loading</code>组件处理组件加载时间过长的等待问题，使用<code>error</code>组件处理加载组件失败的错误提示等，<code>Vue</code> 高级异步组件可以定义需要加载的组件<code>component</code>,加载中显示的组件<code>loading</code>,加载失败的组件<code>error</code>,以及各种延时超时设置，源码同样进入异步组件分支。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Vue.component(<span class="string">&#x27;asyncComponent&#x27;</span>, <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">  <span class="comment">// 需要加载的组件 (应该是一个 `Promise` 对象)</span></span><br><span class="line">  component: <span class="keyword">import</span>(<span class="string">&#x27;./MyComponent.vue&#x27;</span>),</span><br><span class="line">  <span class="comment">// 异步组件加载时使用的组件</span></span><br><span class="line">  loading: LoadingComponent,</span><br><span class="line">  <span class="comment">// 加载失败时使用的组件</span></span><br><span class="line">  error: ErrorComponent,</span><br><span class="line">  <span class="comment">// 展示加载时组件的延时时间。默认值是 200 (毫秒)</span></span><br><span class="line">  delay: <span class="number">200</span>,</span><br><span class="line">  <span class="comment">// 如果提供了超时时间且组件加载也超时了，</span></span><br><span class="line">  <span class="comment">// 则使用加载失败时使用的组件。默认值是：`Infinity`</span></span><br><span class="line">  timeout: <span class="number">3000</span></span><br><span class="line">&#125;))</span><br></pre></td></tr></table></figure>

<p>异步组件函数执行后返回一个对象，并且对象的<code>component</code>执行会返回一个<code>promise</code>对象，因此进入高级异步组件处理分支。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (isObject(res)) &#123;</span><br><span class="line">  <span class="keyword">if</span> (isPromise(res)) &#123;&#125;</span><br><span class="line">  <span class="comment">// 返回对象，且res.component返回一个promise对象，进入分支</span></span><br><span class="line">  <span class="comment">// 高级异步组件处理分支</span></span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">if</span> (isPromise(res.component)) &#123;</span><br><span class="line">    <span class="comment">// 和promise异步组件处理方式相同</span></span><br><span class="line">    res.component.then(resolve, reject);</span><br><span class="line">    ···</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>异步组件会等待响应成功失败的结果，与此同时，代码继续同步执行。高级选项设置中如果设置了<code>error</code>和<code>loading</code>组件，会同时创建两个子类的构造器,</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (isDef(res.error)) &#123;</span><br><span class="line">  <span class="comment">// 异步错误时组件的处理，创建错误组件的子类构造器，并赋值给errorComp</span></span><br><span class="line">  factory.errorComp = ensureCtor(res.error, baseCtor);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (isDef(res.loading)) &#123;</span><br><span class="line">  <span class="comment">// 异步加载时组件的处理，创建错误组件的子类构造器，并赋值给errorComp</span></span><br><span class="line">  factory.loadingComp = ensureCtor(res.loading, baseCtor);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果存在<code>delay</code>属性,则通过<code>settimeout</code>设置<code>loading</code>组件显示的延迟时间。<code>factory.loading</code>属性用来标注是否是显示<code>loading</code>组件。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (res.delay === <span class="number">0</span>) &#123;</span><br><span class="line">  factory.loading = <span class="literal">true</span>;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="comment">// 超过时间会成功加载，则执行失败结果</span></span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (isUndef(factory.resolved) &amp;&amp; isUndef(factory.error)) &#123;</span><br><span class="line">      factory.loading = <span class="literal">true</span>;</span><br><span class="line">      forceRender(<span class="literal">false</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, res.delay || <span class="number">200</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果在<code>timeout</code>时间内，异步组件还未执行<code>resolve</code>的成功结果，即<code>resolve</code>没有赋值,则进行<code>reject</code>失败处理。</p>
<p>接下来依然是渲染注释节点或者渲染<code>loading</code>组件，等待异步处理结果，根据处理结果重新渲染视图节点，和正常的组件渲染流程一样，<code>Vue</code> 只是提供了一种更加强大和灵魂的方式调用异步组件</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><code>Vue</code> 组件注册有两种方式<strong>全局注册</strong>和<strong>局部注册</strong>，它们两者本质上都是调用 <code>Vue.extend</code> 方法得到一个子类构造器，区别在于执行的时机不同，所以子类构造器挂载的上下文也不一样，全局注册挂载在 <code>Vue</code> 根实例上，局部注册挂载在子组件实例对象上。接着分析组件的渲染流程，渲染流程分为两步，一是 <code>render</code> 将 <code>with</code> 函数转化为 <code>Vnode tree</code>, 二是 <code>patch</code> 将 <code>Vnode tree</code> 转化为真实的 <code>DOM</code>, 如果 patch 执行 <code>creatElem</code> 遇到子组件【安装了组件钩子】，会再次走组件渲染流程，这个过程会经过深度递归，最终生成我们熟悉的 <code>DOM tree</code></p>
<p>然后介绍 <code>initLifecycle</code> 是如何建立组件和组件之前的层级关系，最后分析组件的另外两种使用场景：函数组件和异步组件</p>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="mailto:undefined">志非</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://fif.gitee.io/blog/2021/05/04/vue/vue-code-8.component/">https://fif.gitee.io/blog/2021/05/04/vue/vue-code-8.component/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://fif.gitee.io/blog" target="_blank">Jeffery Blog</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/blog/tags/vue/">vue</a><a class="post-meta__tags" href="/blog/tags/vue-%E6%BA%90%E7%A0%81/">vue 源码</a></div><div class="post_share"><div class="social-share" data-image="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/social-share.js/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/social-share.js/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/blog/2021/05/14/vue/vue-code-9.event/"><img class="prev-cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png" onerror="onerror=null;src='/blog/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">（Vue源码七）Vue 事件</div></div></a></div><div class="next-post pull-right"><a href="/blog/2021/04/30/vue/vue-code-7-compiler/"><img class="next-cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/vuetemplate.jpg" onerror="onerror=null;src='/blog/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">（Vue源码五）Vue 编译原理</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span> 相关推荐</span></div><div class="relatedPosts-list"><div><a href="/blog/2021/04/12/vue/vue-code-3.build/" title="（Vue源码一）Vue 源码设计和构建流程"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/Vue.js-i18n-768x512.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-04-12</div><div class="title">（Vue源码一）Vue 源码设计和构建流程</div></div></a></div><div><a href="/blog/2021/04/17/vue/vue-code-4.debugger/" title="（Vue源码二）Vue实例挂载流程"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/1_b2Ig4HR2UOQEV5I2QnQrrw.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-04-17</div><div class="title">（Vue源码二）Vue实例挂载流程</div></div></a></div><div><a href="/blog/2021/04/23/vue/vue-code-5.mergeOptions/" title="（Vue源码三）Vue 丰富的选项合并策略"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/vue-highcharts_1200.webp" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-04-23</div><div class="title">（Vue源码三）Vue 丰富的选项合并策略</div></div></a></div><div><a href="/blog/2021/04/30/vue/vue-code-7-compiler/" title="（Vue源码五）Vue 编译原理"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/vuetemplate.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-04-30</div><div class="title">（Vue源码五）Vue 编译原理</div></div></a></div><div><a href="/blog/2021/04/27/vue/vue-code-6.reactive/" title="（Vue源码四）数据响应式原理"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/mvvm.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-04-27</div><div class="title">（Vue源码四）数据响应式原理</div></div></a></div><div><a href="/blog/2021/05/14/vue/vue-code-9.event/" title="（Vue源码七）Vue 事件"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-05-14</div><div class="title">（Vue源码七）Vue 事件</div></div></a></div></div></div><hr/><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="fas fa-comments fa-fw"></i><span> 评论</span></div></div><div class="comment-wrap"><div><div class="vcomment" id="vcomment"></div></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="card-info-avatar is-center"><img class="avatar-img" src= "" data-lazy-src="/blog/img/home/avatar.jpeg" onerror="this.onerror=null;this.src='/blog/img/friend_404.gif'" alt="avatar"/><div class="author-info__name">志非</div><div class="author-info__description">自己变得更好，生活才会更美好</div></div><div class="card-info-data"><div class="card-info-data-item is-center"><a href="/blog/archives/"><div class="headline">文章</div><div class="length-num">12</div></a></div><div class="card-info-data-item is-center"><a href="/blog/tags/"><div class="headline">标签</div><div class="length-num">6</div></a></div><div class="card-info-data-item is-center"><a href="/blog/categories/"><div class="headline">分类</div><div class="length-num">7</div></a></div></div><a class="button--animated" id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/JefferyXZF"><i class="fab fa-github"></i><span>Follow Me</span></a><div class="card-info-social-icons is-center"><a class="social-icon" href="https://github.com/JefferyXZF" target="_blank" title="Github"><i class="fab fa-github"></i></a><a class="social-icon" href="https://www.yuque.com/jeffery-kkrpn/uz24n9" target="_blank" title="语雀"><i class="fa fa-folder-open"></i></a><a class="social-icon" href="https://mail.qq.com/" target="_blank" title="Email"><i class="fas fa-envelope"></i></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn card-announcement-animation"></i><span>公告</span></div><div class="announcement_content">越努力越幸运</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%BB%84%E4%BB%B6%E6%B8%B2%E6%9F%93%E6%B5%81%E7%A8%8B%E5%9B%BE"><span class="toc-number">1.</span> <span class="toc-text">组件渲染流程图</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%80%9D%E8%80%83"><span class="toc-number">2.</span> <span class="toc-text">思考</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A6%82%E8%A6%81"><span class="toc-number">3.</span> <span class="toc-text">概要</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%BB%84%E4%BB%B6%E4%B8%A4%E7%A7%8D%E6%B3%A8%E5%86%8C%E6%96%B9%E5%BC%8F"><span class="toc-number">4.</span> <span class="toc-text">组件两种注册方式</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%85%A8%E5%B1%80%E6%B3%A8%E5%86%8C"><span class="toc-number">4.1.</span> <span class="toc-text">全局注册</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%B1%80%E9%83%A8%E6%B3%A8%E5%86%8C"><span class="toc-number">4.2.</span> <span class="toc-text">局部注册</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%B3%A8%E5%86%8C%E8%BF%87%E7%A8%8B%E5%88%86%E6%9E%90"><span class="toc-number">4.3.</span> <span class="toc-text">注册过程分析</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%BB%84%E4%BB%B6%E6%B8%B2%E6%9F%93%E6%B5%81%E7%A8%8B"><span class="toc-number">5.</span> <span class="toc-text">组件渲染流程</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%85%B7%E4%BD%93%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90"><span class="toc-number">5.1.</span> <span class="toc-text">具体流程分析</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%BB%84%E4%BB%B6-Vnode-%E6%B8%B2%E6%9F%93%E7%9C%9F%E5%AE%9E-DOM"><span class="toc-number">6.</span> <span class="toc-text">组件 Vnode 渲染真实 DOM</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%85%B7%E4%BD%93%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90-1"><span class="toc-number">6.1.</span> <span class="toc-text">具体流程分析</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%BB%BA%E7%AB%8B%E7%BB%84%E4%BB%B6%E5%85%B3%E8%81%94"><span class="toc-number">7.</span> <span class="toc-text">建立组件关联</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6"><span class="toc-number">8.</span> <span class="toc-text">函数式组件</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF"><span class="toc-number">8.1.</span> <span class="toc-text">使用场景</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%87%BD%E6%95%B0%E7%BB%84%E4%BB%B6%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90"><span class="toc-number">8.2.</span> <span class="toc-text">函数组件源码分析</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%BC%82%E6%AD%A5%E7%BB%84%E4%BB%B6"><span class="toc-number">9.</span> <span class="toc-text">异步组件</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%B7%A5%E5%8E%82%E5%87%BD%E6%95%B0"><span class="toc-number">9.1.</span> <span class="toc-text">工厂函数</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BC%82%E6%AD%A5%E7%BB%84%E4%BB%B6%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90"><span class="toc-number">9.2.</span> <span class="toc-text">异步组件流程分析</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Promise%E5%BC%82%E6%AD%A5%E7%BB%84%E4%BB%B6"><span class="toc-number">9.3.</span> <span class="toc-text">Promise异步组件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%AB%98%E7%BA%A7%E5%BC%82%E6%AD%A5%E7%BB%84%E4%BB%B6"><span class="toc-number">9.4.</span> <span class="toc-text">高级异步组件</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%80%BB%E7%BB%93"><span class="toc-number">10.</span> <span class="toc-text">总结</span></a></li></ol></div></div><script defer="defer" data-pjax="data-pjax" src="/blog/js/card_clock.js"></script><div class="card-widget card-clock"><div id="clock"><img v-if="clockshow == &quot;false&quot;" alt="" src= "" data-lazy-src="/blog/img/weather/loading.gif" height="120px" width="100%"/><table class="clock" v-if="clockshow"><tbody><tr><td class="clockdate">{{date}}</td><td class="weatherimg"><img id="weatherimg" :src="weatherimg" alt=""/></td><td class="temperature">{{temperature}}</td><td class="humidityimg"><img id="humidityimg" :src="humidityimg" alt=""/></td><td class="humidity">{{humidity}}</td></tr><tr class="time"><td colspan="5">{{time}}</td></tr><tr><td class="usaqi" colspan="1"><span>{{ip}}</span></td><td class="city" colspan="2">{{city}}</td><td class="daylight" colspan="2">{{daylight}}</td></tr></tbody></table></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/blog/2021/05/24/tools/hexo/advanced/" title="Hexo 博客页面性能优化"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/Hexo-logo.png" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="Hexo 博客页面性能优化"/></a><div class="content"><a class="title" href="/blog/2021/05/24/tools/hexo/advanced/" title="Hexo 博客页面性能优化">Hexo 博客页面性能优化</a><time datetime="2021-05-23T16:00:00.000Z" title="发表于 2021-05-24 00:00:00">2021-05-24</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2021/05/14/vue/vue-code-9.event/" title="（Vue源码七）Vue 事件"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="（Vue源码七）Vue 事件"/></a><div class="content"><a class="title" href="/blog/2021/05/14/vue/vue-code-9.event/" title="（Vue源码七）Vue 事件">（Vue源码七）Vue 事件</a><time datetime="2021-05-13T16:00:00.000Z" title="发表于 2021-05-14 00:00:00">2021-05-14</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2021/05/04/vue/vue-code-8.component/" title="（Vue源码六）Vue 组件原理分析"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="（Vue源码六）Vue 组件原理分析"/></a><div class="content"><a class="title" href="/blog/2021/05/04/vue/vue-code-8.component/" title="（Vue源码六）Vue 组件原理分析">（Vue源码六）Vue 组件原理分析</a><time datetime="2021-05-03T16:00:00.000Z" title="发表于 2021-05-04 00:00:00">2021-05-04</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2021/04/30/vue/vue-code-7-compiler/" title="（Vue源码五）Vue 编译原理"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/vuetemplate.jpg" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="（Vue源码五）Vue 编译原理"/></a><div class="content"><a class="title" href="/blog/2021/04/30/vue/vue-code-7-compiler/" title="（Vue源码五）Vue 编译原理">（Vue源码五）Vue 编译原理</a><time datetime="2021-04-29T16:00:00.000Z" title="发表于 2021-04-30 00:00:00">2021-04-30</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2021/04/27/vue/vue-code-6.reactive/" title="（Vue源码四）数据响应式原理"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/mvvm.png" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="（Vue源码四）数据响应式原理"/></a><div class="content"><a class="title" href="/blog/2021/04/27/vue/vue-code-6.reactive/" title="（Vue源码四）数据响应式原理">（Vue源码四）数据响应式原理</a><time datetime="2021-04-26T16:00:00.000Z" title="发表于 2021-04-27 00:00:00">2021-04-27</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2021 By 志非</div><div class="footer_custom_text">Hi, welcome to my <a href="/">blog</a>!</div><div id="workboard"></div><script async="async" src="/blog/js/runtime.js"></script></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="chat_btn" type="button" title="rightside.chat_btn"><i class="fas fa-sms"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="fas fa-comments"></i></a><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="algolia-search"><div class="search-dialog"><div class="search-dialog__title" id="algolia-search-title">Algolia</div><div id="algolia-input-panel"><div id="algolia-search-input"></div></div><hr/><div id="algolia-search-results"><div id="algolia-hits"></div><div id="algolia-pagination"></div><div id="algolia-stats"></div></div><span class="search-close-button"><i class="fas fa-times"></i></span></div><div id="search-mask"></div></div><div><script src="https://cdn.jsdelivr.net/npm/vue"></script><script src="/blog/js/utils.js"></script><script src="/blog/js/main.js"></script><script src="/blog/js/tw_cn.js"></script><script src="https://cdn.jsdelivr.net/npm/medium-zoom/dist/medium-zoom.min.js"></script><script src="https://cdn.jsdelivr.net/npm/instant.page/instantpage.min.js" type="module"></script><script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.iife.min.js"></script><script src="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.js"></script><script src="/blog/js/search/algolia.js"></script><script>var preloader = {
  endLoading: () => {
    document.body.style.overflow = 'auto';
    document.getElementById('loading-box').classList.add("loaded")
  },
  initLoading: () => {
    document.body.style.overflow = '';
    document.getElementById('loading-box').classList.remove("loaded")

  }
}
window.addEventListener('load',preloader.endLoading())</script><div class="js-pjax"><script>if (document.getElementsByClassName('mermaid').length) {
  if (window.mermaidJsLoad) mermaid.init()
  else {
    getScript('https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js').then(() => {
      window.mermaidJsLoad = true
      mermaid.initialize({
        theme: 'default',
      })
      false && mermaid.init()
    })
  }
}</script><script>function loadValine () {
  function initValine () {
    const valine = new Valine(Object.assign({
      el: '#vcomment',
      appId: 'JC72d1A7XBwC26rGCKMVn5CS-MdYXbMMI',
      appKey: 'xI6bG7FseICz8ShKdmWVe7N1',
      placeholder: 'Please leave your footprints',
      avatar: 'https://gitee.com/FIF/pic-beg/raw/master/images/vue/1619248532079-avatar_coment.webp',
      meta: 'nick,mail,link'.split(','),
      pageSize: '10',
      lang: 'zh-CN',
      recordIP: false,
      serverURLs: '',
      emojiCDN: '',
      emojiMaps: "",
      enableQQ: true,
      path: window.location.pathname,
      requiredFields: ["nick,mail"],
      visitor: false,
      avatar_cdn: 'https://www.gravatar.com/avatar/'
    }, null))
  }

  if (typeof Valine === 'function') initValine() 
  else getScript('https://cdn.jsdelivr.net/npm/valine/dist/Valine.min.js').then(initValine)
}

if ('Valine' === 'Valine' || !true) {
  if (true) btf.loadComment(document.getElementById('vcomment'),loadValine)
  else setTimeout(loadValine, 0)
} else {
  function loadOtherComment () {
    loadValine()
  }
}</script></div><div class="aplayer no-destroy" data-id="000PeZCQ1i4XVs" data-server="tencent" data-type="artist" data-fixed="true" data-mini="true" data-listFolded="false" data-order="random" data-preload="none" data-autoplay="false" pjax="true" muted></div><script defer="defer" id="ribbon" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/dist/canvas-ribbon.min.js" size="150" alpha="0.6" zIndex="-1" mobile="false" data-click="true"></script><script id="canvas_nest" defer="defer" color="0,0,255" opacity="0.7" zIndex="-1" count="99" mobile="false" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/dist/canvas-nest.min.js"></script><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/dist/activate-power-mode.min.js"></script><script>POWERMODE.colorful = true;
POWERMODE.shake = true;
POWERMODE.mobile = false;
document.body.addEventListener('input', POWERMODE);
</script><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.js"></script><script src="https://cdn.jsdelivr.net/gh/metowolf/MetingJS@1.2/dist/Meting.min.js"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>